package com.legsem.jhosync.ws.rs;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.legsem.jhosync.ClientUpdatePages;
import com.legsem.jhosync.Server;
import com.legsem.jhosync.change.Changes;
import com.legsem.jhosync.change.ChangesSet;
import com.legsem.jhosync.dto.ClientCUD;

/**
 * @author Fady
 * 
 */
@Path("")
public class ClientResource extends AbstractResource {

    /** Client server protocol version. */
    public static final int RHOSYNC_PROTOCOL_VERSION = 3;

    final Logger logger = LoggerFactory.getLogger(ClientResource.class);

    private JsonFactory _jsonFactory = new JsonFactory();

    @SuppressWarnings("unchecked")
    @GET
    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
    public Response clientQuery(@QueryParam("client_id") String client_id,
            @DefaultValue("2000") @QueryParam("p_size") int p_size,
            @DefaultValue("3") @QueryParam("version") int version,
            @QueryParam("source_name") String source_name,
            @QueryParam("token") String token, @Context HttpServletRequest req) {
        try {
            logger.debug(String
                    .format("ClientResource client_id: %s, p_size: %d, version: %d, source_name: %s, token: %s",
                            client_id, p_size, version, source_name, token));

            if (version < RHOSYNC_PROTOCOL_VERSION) {
                return Response.status(404).build();
            }

            Server server = getServer(req);
            ClientUpdatePages updatePages = server.doQuery(client_id, p_size,
                    version, source_name, token, req.getParameterMap());

            ByteArrayOutputStream osba = new ByteArrayOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(osba, "UTF-8");
            JsonGenerator g = _jsonFactory.createJsonGenerator(osw);

            g.writeStartArray();

            addNumericObject(g, "version", RHOSYNC_PROTOCOL_VERSION);
            addStringObject(g, "token", (updatePages.getToken() == null) ? ""
                    : updatePages.getToken());
            addNumericObject(g, "count", updatePages.getCount());
            addNumericObject(g, "progress_count",
                    updatePages.getProgressCount());
            addNumericObject(g, "total_count", updatePages.getTotalCount());
            addChanges(g, updatePages.getPageChanges());

            g.writeEndArray();

            g.close();
            osw.close();

            return Response.ok(osba.toByteArray()).build();
        } catch (Exception e) {
            logger.error("Failed to service query request for source "
                    + source_name + " for client " + client_id, e);
            throw new WebApplicationException(e,
                    Response.Status.INTERNAL_SERVER_ERROR);
        }

    }

    @POST
    @Consumes("application/json")
    public Response clientCUD(InputStream clientCUDPayload,
            @Context HttpServletRequest req) {
        try {
            ClientCUD clientCUD = getMapper().readValue(clientCUDPayload,
                    ClientCUD.class);
            Server server = getServer(req);
            server.doCUD(clientCUD.getClient_id(), clientCUD.getSource_name(),
                    clientCUD.getCreate(), clientCUD.getUpdate(),
                    clientCUD.getDelete());
            return Response.ok().build();

        } catch (Exception e) {
            logger.error("Failed to service update request", e);
            throw new WebApplicationException(e,
                    Response.Status.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * Add the changes to the JSON reply.
     * 
     * @param g the JSON generator
     * @param changes the set of changes
     * @throws IOException if JSON serialization fails
     */
    protected void addChanges(JsonGenerator g, ChangesSet changes)
            throws IOException {

        addChanges(g, "insert", changes.getInserts());
        addChanges(g, "delete", changes.getDeletes());
        addChanges(g, "links", changes.getCreateLinks());
        addChanges(g, "source-error", changes.getSourceErrors());
        addChanges(g, "create-error", changes.getCreateErrors());
        addChanges(g, "update-error", changes.getUpdateErrors());
        addChanges(g, "delete-error", changes.getDeleteErrors());
        addStringObject(g, "metadata", changes.getMetadata());
    }

    /**
     * Generate JSON content for a type of changes.
     * 
     * @param g the JSON generator
     * @param type the type of changes
     * @param changes the changes
     * @throws IOException if JSON serialization fails
     */
    protected void addChanges(JsonGenerator g, String type, Changes changes)
            throws IOException {
        if (changes != null && !changes.empty()) {
            addMapObject(g, type, changes.get());
        }
    }

    protected void addNumericObject(JsonGenerator g, String name, int value)
            throws IOException {
        g.writeStartObject();
        g.writeFieldName(name);
        g.writeNumber(value);
        g.writeEndObject();
    }

    protected void addStringObject(JsonGenerator g, String name, String value)
            throws IOException {
        if (value != null) {
            g.writeStartObject();
            g.writeFieldName(name);
            g.writeString(value);
            g.writeEndObject();
        }
    }

    protected void addMapObject(JsonGenerator g, String name,
            Map < String, ? > value) throws IOException {
        g.writeStartObject();
        g.writeFieldName(name);
        getMapper().writeValue(g, value);
        g.writeEndObject();
    }
}
